package com.autonavi.yunda.yunji.core.service;

import com.autonavi.yunda.yunji.common.enums.EditType;
import com.autonavi.yunda.yunji.common.exception.AmapException;
import com.autonavi.yunda.yunji.common.exception.AmapExceptionCode;
import com.autonavi.yunda.yunji.common.exception.AmapPreconditions;
import com.autonavi.yunda.yunji.common.utils.JsonUtils;
import com.autonavi.yunda.yunji.common.utils.http.HttpClient;
import com.autonavi.yunda.yunji.common.utils.http.vo.HttpResponse;
import com.autonavi.yunda.yunji.core.config.ApplicationEnvConfig;
import com.autonavi.yunda.yunji.core.enums.InterfaceStatus;
import com.autonavi.yunda.yunji.core.model.condition.InterfaceQueryCondition;
import com.autonavi.yunda.yunji.core.model.dto.InterfaceConfigDTO;
import com.autonavi.yunda.yunji.core.model.dto.ProjectDTO;
import com.autonavi.yunda.yunji.core.model.mappers.InterfaceConfigMapper;
import com.autonavi.yunda.yunji.core.model.mappers.TaskConfigMapper;
import com.autonavi.yunda.yunji.core.model.vo.InterfaceData;
import com.autonavi.yunda.yunji.core.utils.ValidUtil;
import com.autonavi.yunda.yunji.core.vo.EngineData;
import com.autonavi.yunda.yunji.core.vo.EngineInterfaceConfigVO;
import com.autonavi.yunda.yunji.core.vo.ProjectExtraData;
import com.autonavi.yunda.yunji.core.vo.response.InterfaceUriGroupVO;
import com.autonavi.yunda.yunji.core.vo.response.SimpleInterfaceConfigVO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.slf4j.MDC;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.sql.Timestamp;
import java.util.*;
import java.util.stream.Collectors;

import static com.autonavi.yunda.yunji.common.constant.CommonConstant.HEADER_TRACE_ID;
import static com.autonavi.yunda.yunji.common.constant.CommonConstant.MDC_TRACE_ID;

/**
 * @author cizhong.wcs
 * @date 2022/3/7 7:37 下午
 */
@Slf4j
@Component
public class InterfaceConfigService implements IInterfaceConfigService {
    @Resource
    IIpService ipService;
    @Resource
    ApplicationEnvConfig applicationEnvConfig;
    @Resource
    InterfaceConfigMapper interfaceConfigMapper;
    @Resource
    PartUnitService partUnitService;
    @Resource
    ProjectService projectService;
    @Resource
    TaskConfigMapper taskConfigMapper;

    static HttpClient httpClient = new HttpClient();

    @Override
    public EngineInterfaceConfigVO fetchCurrentValidScriptVO(String uri) {
        InterfaceConfigDTO dto = fetchCurrentValidScript(uri);
        if (dto == null) {
            return null;
        }
        EngineInterfaceConfigVO vo = new EngineInterfaceConfigVO();
        BeanUtils.copyProperties(dto, vo);
        ProjectDTO project = projectService.findByProjectId(dto.getProjectId());
        ProjectExtraData projectExtraData = (project != null && project.getExtraData() != null) ? project.getExtraData() : new ProjectExtraData();
        vo.setProjectExtraData(projectExtraData);
        return vo;
    }

    @Override
    public List<String> listNewPublishedUris(Collection<String> existsUris) {
        QueryWrapper<InterfaceConfigDTO> queryWrapper = new QueryWrapper<>();
        queryWrapper.in("status", Lists.newArrayList(InterfaceStatus.PUBLISHED, InterfaceStatus.GRAY));
        if (!existsUris.isEmpty()) {
            queryWrapper.notIn("uri", existsUris);
        }
        return list(queryWrapper).stream().map(InterfaceConfigDTO::getUri).collect(Collectors.toList());
    }


    public Long add(InterfaceConfigDTO interfaceDTO) {
        List<InterfaceConfigDTO> sameUriVersionList = list(InterfaceQueryCondition.builder().uri(interfaceDTO.getUri()).version(interfaceDTO.getVersion()).build());
        AmapPreconditions.checkArgumentForUser(CollectionUtils.isEmpty(sameUriVersionList), "创建失败,该版本URI已存在,不允许重复创建");
        interfaceConfigMapper.insert(interfaceDTO);
        return interfaceDTO.getId();
    }

    public void update(UpdateWrapper<InterfaceConfigDTO> updateWrapper) {
        interfaceConfigMapper.update(null, updateWrapper);
    }

    public InterfaceConfigDTO findByIdOrThrow(Long id) {
        InterfaceConfigDTO dto = interfaceConfigMapper.selectById(id);
        AmapPreconditions.checkArgumentForUser(dto != null, "不存在该接口配置 id = " + id);
        return dto;
    }

    public String getProjectIdOrNull(Long interfaceId, String editType) {
        if (interfaceId == null) {
            return null;
        }
        if (EditType.TASK.name().equalsIgnoreCase(editType)) {
            return taskConfigMapper.selectById(interfaceId).getProjectId();
        } else {
            return interfaceConfigMapper.selectById(interfaceId).getProjectId();
        }
    }

    public List<InterfaceConfigDTO> list(InterfaceQueryCondition condition) {
        LambdaQueryWrapper<InterfaceConfigDTO> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(condition.getId() != null, InterfaceConfigDTO::getId, condition.getId());
        wrapper.eq(StringUtils.isNotBlank(condition.getUri()), InterfaceConfigDTO::getUri, condition.getUri());
        wrapper.eq(StringUtils.isNotBlank(condition.getProjectId()), InterfaceConfigDTO::getProjectId, condition.getProjectId());
        wrapper.eq(StringUtils.isNotBlank(condition.getVersion()), InterfaceConfigDTO::getVersion, condition.getVersion());
        wrapper.eq(condition.getStatus() != null, InterfaceConfigDTO::getStatus, condition.getStatus());
        wrapper.orderByDesc(InterfaceConfigDTO::getId);
        return interfaceConfigMapper.selectList(wrapper);
    }

    /**
     * 1. 按条件分页查询接口列表
     * 2. 并对结果按uri进行分组，使得同一uri不同版本的接口信息相邻展示
     * @param condition
     * @return
     */
    public InterfaceUriGroupVO listInterfaceUriGroup(InterfaceQueryCondition condition) {
        if (condition.projectIds != null && condition.projectIds.isEmpty()) {
            return InterfaceUriGroupVO.empty();
        }
        // 查询去重后的uri集合，并对其去重分页，从而获取当前页需要展示的所有的uri
        Page<String> pageRes = interfaceConfigMapper.listDistinctUri(new Page<>(condition.pageNo, condition.pageSize), condition);
        if (pageRes.getRecords().size() == 0) {
            return new InterfaceUriGroupVO(new ArrayList<>(), pageRes.getTotal());
        }
        // 只查询出当前页的接口信息
        condition.uris = pageRes.getRecords();
        List<SimpleInterfaceConfigVO> dtoList = interfaceConfigMapper.listSimpleInterfaceConfig(condition);
        // 对结果按uri进行分组， 从而每个uri的不同版本相邻展示
        Map<String, List<SimpleInterfaceConfigVO>> uriInterfaceListMap = new LinkedHashMap<>();
        for (SimpleInterfaceConfigVO vo : dtoList) {
            if (uriInterfaceListMap.get(vo.getUri()) != null) {
                uriInterfaceListMap.get(vo.getUri()).add(vo);
            } else {
                List<SimpleInterfaceConfigVO> subVoList = new ArrayList<>();
                subVoList.add(vo);
                uriInterfaceListMap.put(vo.getUri(), subVoList);
            }
        }
        ArrayList<List<SimpleInterfaceConfigVO>> uriGroup = new ArrayList<>(uriInterfaceListMap.values());
        for (List<SimpleInterfaceConfigVO> simpleInterfaceConfigVOS : uriGroup) {
            simpleInterfaceConfigVOS.sort(Comparator.comparing(interfaceConfigVO -> interfaceConfigVO.getStatus().getDisplayPriority()));
        }
        return new InterfaceUriGroupVO(uriGroup, pageRes.getTotal());
    }

    @Transactional
    public Long enterValidating(InterfaceConfigDTO validatingDto) {
        AmapPreconditions.checkArgumentForUser(
                applicationEnvConfig.isPrepub() || applicationEnvConfig.isPublish(),
                "只有预发和生产环境可以进入待验证状态"
        );
        InterfaceConfigDTO existsDto = fetchByUriVersion(validatingDto.getUri(), validatingDto.getVersion());
        if (existsDto == null) {
            interfaceConfigMapper.insert(validatingDto);
        } else {
            AmapPreconditions.checkArgumentForUser(
                    !(
                            (existsDto.getStatus().equals(InterfaceStatus.PUBLISHED) || existsDto.getStatus().equals(InterfaceStatus.GRAY))
                                    && applicationEnvConfig.isPublish()
                    ),
                    "该接口在生产环境已经发布或灰度,请取消灰度或升级接口版本");
            validatingDto.setId(existsDto.getId());
            interfaceConfigMapper.updateById(validatingDto);
            clearGrayIps(validatingDto.getId());
        }
        return validatingDto.getId();
    }

    private InterfaceConfigDTO fetchByUriVersion(String uri, String version) {
        AmapPreconditions.checkNotNull(uri);
        AmapPreconditions.checkNotNull(version);
        QueryWrapper<InterfaceConfigDTO> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("uri", uri);
        queryWrapper.eq("version", version);
        return interfaceConfigMapper.selectOne(queryWrapper);
    }

    public void grayPublish(Long id, List<String> ips) {
        InterfaceConfigDTO dto = findByIdOrThrow(id);
        for (String ip : ips) {
            ValidUtil.checkRegex("ip", ip, "([0,1]?\\d{1,2}|2([0-4][0-9]|5[0-5]))(\\.([0,1]?\\d{1,2}|2([0-4][0-9]|5[0-5]))){3}");
        }
        AmapPreconditions.checkArgumentForUser(
                dto.getStatus().equals(InterfaceStatus.TEST_SUCCESS) || dto.getStatus().equals(InterfaceStatus.GRAY),
                "接口调试成功才能进行灰度,当前状态:" + dto.getStatus().getDesc()
        );
        InterfaceConfigDTO sameUriGrayDto = list(InterfaceQueryCondition.builder()
                .uri(dto.getUri()).status(InterfaceStatus.GRAY).build()).stream().findFirst().orElse(null);
        if (sameUriGrayDto != null && !dto.getVersion().equals(sameUriGrayDto.getVersion())) {
            throw new AmapException(AmapExceptionCode.COMMON_SERVER_WARN, "该接口已有其他灰度版本");
        }
        appendGrayIps(id, ips);
    }

    @Transactional
    public void cancelGrayPublish(Long id) {
        InterfaceConfigDTO configDTO = findByIdOrThrow(id);
        AmapPreconditions.checkArgumentForUser(configDTO.getStatus().equals(InterfaceStatus.GRAY), "灰度状态的接口才能取消灰度,当前接口状态为:" + configDTO.getStatus().getDesc());
        updateStatus(id, InterfaceStatus.TEST_SUCCESS);
        clearGrayIps(id);
    }

    @Transactional
    public void publish(Long id) {
        InterfaceConfigDTO dto = findByIdOrThrow(id);
        AmapPreconditions.checkArgumentForUser(!dto.getStatus().equals(InterfaceStatus.PUBLISHED), "接口该版本" + dto.getVersion() + "已发布过,无需再发布");
        AmapPreconditions.checkArgumentForUser(
                dto.getStatus().equals(InterfaceStatus.TEST_SUCCESS) || dto.getStatus().equals(InterfaceStatus.GRAY),
                "接口调试成功才能进行发布,当前状态:" + dto.getStatus().getDesc());
        InterfaceConfigDTO sameUriPublished = list(InterfaceQueryCondition.builder()
                .uri(dto.getUri())
                .status(InterfaceStatus.PUBLISHED)
                .build()).stream().findFirst().orElse(null);
        updateStatus(id, InterfaceStatus.PUBLISHED);
        if (sameUriPublished != null) {
            updateStatus(sameUriPublished.getId(), InterfaceStatus.OFFLINE);
        }
        clearGrayIps(id);

    }

    public void forceTestSuccess(Long id) {
        InterfaceConfigDTO dto = findByIdOrThrow(id);
        AmapPreconditions.checkArgumentForUser(
                dto.getStatus().equals(InterfaceStatus.INIT) ||
                        dto.getStatus().equals(InterfaceStatus.VALIDATING) ||
                        dto.getStatus().equals(InterfaceStatus.OFFLINE),
                "编辑中,验证中或已线下的接口才能强制为调试成功");
        updateStatus(id, InterfaceStatus.TEST_SUCCESS);
    }

    public void offline(Long id) {
        InterfaceConfigDTO dto = findByIdOrThrow(id);
        AmapPreconditions.checkArgumentForUser(dto.getStatus().equals(InterfaceStatus.PUBLISHED), "发布中的接口才能下线");
        updateStatus(id, InterfaceStatus.OFFLINE);
    }

    public void updateStatus(Long id, InterfaceStatus status) {
        UpdateWrapper<InterfaceConfigDTO> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("id", id);
        updateWrapper.set("status", status);
        interfaceConfigMapper.update(null, updateWrapper);
    }

    public void updateInterfaceData(Long id, InterfaceData interfaceData) {
        UpdateWrapper<InterfaceConfigDTO> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("id", id);
        updateWrapper.set("interface_data", JsonUtils.toString(interfaceData));
        interfaceConfigMapper.update(null, updateWrapper);
    }


    public void clearGrayIps(Long id) {
        UpdateWrapper<InterfaceConfigDTO> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("id", id);
        updateWrapper.set("gray_ips", null);
        interfaceConfigMapper.update(null, updateWrapper);
    }

    public void appendGrayIps(Long id, List<String> grayIps) {
        InterfaceConfigDTO dto = findByIdOrThrow(id);
        Set<String> allIps = new HashSet<>();
        if (dto.getGrayIps() != null) {
            allIps.addAll(Arrays.stream(dto.getGrayIps().split(",")).collect(Collectors.toSet()));
        }
        allIps.addAll(grayIps);
        UpdateWrapper<InterfaceConfigDTO> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("id", id);
        updateWrapper.set("status", InterfaceStatus.GRAY);
        updateWrapper.set("gray_ips", String.join(",", allIps));
        interfaceConfigMapper.update(null, updateWrapper);
    }

    public List<InterfaceConfigDTO> list(QueryWrapper<InterfaceConfigDTO> queryWrapper) {
        return interfaceConfigMapper.selectList(queryWrapper);
    }

    /**
     * 获取当前正在发布生效的接口
     */
    public InterfaceConfigDTO fetchCurrentValidScript(String uri) {
        InterfaceConfigDTO grayDto = list(InterfaceQueryCondition.builder().status(InterfaceStatus.GRAY).uri(uri).build()).stream().findFirst().orElse(null);
        if (grayDto != null && grayDto.getGrayIps() != null) {
            if (Arrays.stream(grayDto.getGrayIps().split(",")).anyMatch(ip -> ipService.fetchLocalhostIp().equals(ip))) {
                return grayDto;
            }
        }
        return list(InterfaceQueryCondition.builder().status(InterfaceStatus.PUBLISHED).uri(uri).build())
                .stream().min(Comparator.comparing(dto -> -dto.getCreateTime().getTime()))
                .orElse(null);
    }

    /**
     * 通知引擎更新接口
     */
    public void notifyEngine(Long id) {
        try {
            List<String> ipList = ipService.fetchEngineIpList();
            for (String ip : ipList) {
                try {
                    HttpResponse res = httpClient.get(
                            "http://" + ip + "/_refreshScriptClassCache?id=" + id,
                            Map.of(HEADER_TRACE_ID, MDC.get(MDC_TRACE_ID) == null ? "" : MDC.get(MDC_TRACE_ID))
                    );
                    log.info("notifyEngine ip =  {},res = {}", ip, JsonUtils.toString(res));
                } catch (Exception e) {
                    log.error("notifyEngine error, ip =  {}", ip, e);
                }
            }
        } catch (Exception e) {
            log.error("notifyEngine error id = {}", id, e);
        }
    }


    public Map<String, Long> getProjectCountMap(List<String> projectIds) {
        HashMap<String, Long> result = new HashMap<>();
        if (!projectIds.isEmpty()) {
            for (Map<String, Object> map : interfaceConfigMapper.getProjectCountMap(projectIds)) {
                result.put((String) map.get("project_id"), (Long) map.get("uriCount"));
            }
        }
        return result;
    }

    /**
     * 更新接口依赖的零部件id列表
     */
    public void updateComponentIdList(Long id) {
        InterfaceConfigDTO dto = findByIdOrThrow(id);
        EngineData engineData = new EngineData(dto.getEngineData());
        List<Long> partIdList = new ArrayList<>();
        List<Long> unitIdList = new ArrayList<>();
        engineData.nodes.stream().filter(n -> n.nodeType.isYundaComponent())
                .forEach(baseNode -> {
                    if ("part".equals(baseNode.getComponentType())) {
                        partIdList.add(Long.valueOf(baseNode.getComponentId()));
                    } else if ("unit".equals(baseNode.getComponentType())) {
                        unitIdList.add(Long.valueOf(baseNode.getComponentId()));
                    }
                });
        InterfaceData interfaceData = dto.getInterfaceData();
        interfaceData.partIdList = partIdList;
        interfaceData.unitIdList = unitIdList;
        updateInterfaceData(id, interfaceData);
    }

    public void delete(Long id) {
        interfaceConfigMapper.deleteById(id);
    }

    public Long upgrade(Long id, String empId) {
        InterfaceConfigDTO dto = findByIdOrThrow(id);
        String version = findLatestVersionInterface(dto.getUri()).getVersion();
        dto.setId(null);
        dto.setStatus(InterfaceStatus.INIT);
        dto.setVersion(Integer.valueOf(version) + 1 + "");
        dto.setInterfaceData(new InterfaceData());
        dto.setCreateTime(null);
        dto.setUpdateTime(null);
        dto.setCreateUser(empId);
        dto.setUpdateUser(empId);
        interfaceConfigMapper.insert(dto);
        return dto.getId();
    }

    private InterfaceConfigDTO findLatestVersionInterface(String uri) {
        QueryWrapper<InterfaceConfigDTO> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("uri", uri);
        queryWrapper.last("order by CAST(`version` AS SIGNED) desc limit 1");
        InterfaceConfigDTO dto = interfaceConfigMapper.selectOne(queryWrapper);
        AmapPreconditions.checkArgumentForUser(dto != null, "无法升级版本,不存在该uri接口:" + uri);
        return dto;
    }

    @Transactional
    public void convertToComponent(Long id, String userId) {
        InterfaceConfigDTO dto = findByIdOrThrow(id);
        AmapPreconditions.checkArgumentForUser(dto.getStatus().equals(InterfaceStatus.PUBLISHED), "已发布的接口才可以转换为零部件");
        ProjectDTO projectDto = projectService.findByProjectIdOrThrow(dto.getProjectId());
        if (dto.getInterfaceData().partUnitId == null) {
            dto.getInterfaceData().partUnitId = partUnitService.addByInterface(dto, userId, projectDto.getBdId());
            updateInterfaceData(id, dto.getInterfaceData());
        } else {
            partUnitService.updateByInterface(dto, userId, projectDto.getBdId());
        }
    }

    public void updateExposedRestapi(Long id, Boolean exposedByRestapi, String userId) {
        InterfaceConfigDTO dto = findByIdOrThrow(id);
        dto.setExposedByRestapi(exposedByRestapi);
        dto.setUpdateTime(new Timestamp(DateTime.now().getMillis()));
        dto.setUpdateUser(userId);
        interfaceConfigMapper.updateById(dto);
    }
}
